using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

using System.Net;
using System.IO;
using System.Threading;
using UnityEngine;
using Frankfort.Threading.Internal;

public class HttpDownloadHelperEx
{
    public delegate void SetStep(DownloadStep step);

    public delegate void RepsonseAddTempSum(int sum);

    public delegate void RepsonseSuccess();

    public delegate void ResponseError(DownloadError status);

    private const int DefaultTimeout = 5 * 1000;
    private const int BUFFER_SIZE = 512 * 1024;
    private const bool testFileLog = true;

    private HttpRequestEx _request;
    private bool _isCancel;
    private bool writeTimeLog;
    private int _progress;

    public int progress
    {
        get
        {
            return _progress;
        }
    }
    //url: redirected url
    //_localPath: the path using to save downloading data
    //_originalUrl: original url, use for log
    //_offset: offset of big file
    //_size: download size, size of small file
    //_totalSize: size of big file
    //readedSum: aleardy downloaded size, size of small file
    //setStep: set step
    //repsonseAddTempSum: progress call back
    //repsonseSuccess: success call back
    //responseError: error call back
    public void download(bool createThread, string url, string _localPath, string _originalUrl, uint _offset, uint _size, int readedSum, uint _totalSize, SetStep setStep, RepsonseSuccess repsonseSuccess, ResponseError responseError)
    {
        _progress = readedSum;
        if (createThread)
        {
            Thread _workThread = SingleThreadStarter.StartSingleThread(() =>
                {
                    realDownload(url, _localPath, _originalUrl, _offset, _size, readedSum, _totalSize, setStep, repsonseSuccess, responseError);
                });
			
            _workThread.Name = _originalUrl + "|" + _offset + "|" + _size;
        }
        else
        {
            realDownload(url, _localPath, _originalUrl, _offset, _size, readedSum, _totalSize, setStep, repsonseSuccess, responseError);
        }
    }

    void realDownload(string url, string _localPath, string _originalUrl, uint _offset, uint _size, int readedSum, uint _totalSize, SetStep setStep, RepsonseSuccess repsonseSuccess, ResponseError responseError)
    {
        bool downloaded = false;
        DownloadError error = DownloadError.None;
        byte[] BufferRead = null;
        FileStream outFileStream = null;
        do
        {
            if (_isCancel)
            {
                break;
            }
            if (_size > 0 && _size == readedSum)
            {
                downloaded = true;
                break;
            }

            lock (this)
            {
                if (_isCancel)
                {
                    break;
                }
                try
                {
                    _request = HttpRequestEx.Create(url);
                }
                catch (Exception e)
                {
                    Debug.LogException(e);
                    continue;
                }
            }

            _request.Method = "GET";
            _request.ConnectionLimit = HttpDownloadManager.maxRuningCount;
            _request.Timeout = DefaultTimeout * 4;
            _request.ReadWriteTimeout = DefaultTimeout * 4;
            int realOffset = (int)_offset + readedSum;
            int realSize = -1;
            if(_size > 0)
            {
                realSize = (int)_offset + (int)_size;
                _request.AddRange(realOffset, realSize - 1);

//                File.AppendAllText(_localPath + ".download_log", realOffset + ":" + (realSize - 1) + "\n");
            }

            HttpResponseEx response = null;
            try
            {
                setStep(DownloadStep.Connecting);
                response = _request.GetResponse();
                response.GetStream();
                long len = response.ContentLength;
                string encoding = null;
                try
                {
                    encoding = response.Headers["Content-Encoding"];
                }
                catch (System.Exception e)
                {
                }
                if (!string.IsNullOrEmpty(encoding))
                {
                    throw new EncodingException("encoding is " + encoding + ", must set null");
                }
                string contentRange = null;

                try
                {
                    contentRange = response.Headers["Content-Range"];
                }
                catch (System.Exception e)
                {
                }

                if (!string.IsNullOrEmpty(contentRange))
                {
                    if(realSize > 0)
                    {
                        string nowContentRange = "bytes " + realOffset + "-" + (realSize - 1) + "/" + _totalSize;
                        if (contentRange != nowContentRange)
                        {
                            throw new SizeException("content-range is error: " + contentRange + "(" + contentRange.Length + ")" + ", need " + nowContentRange + "(" + nowContentRange.Length + ")" + " | " + url);
                        }
                    }
                }

                if (len > 0)
                {
                    if(realSize > 0)
                    {
                        int s = realSize - realOffset;
                        if (len != s)
                        {
                            throw new SizeException("size " + len + " is error, need " + s + " | " + url);
                        }
                    }
                }

                setStep(DownloadStep.Connected);
                response.ReadTimeout = DefaultTimeout;
                if (BufferRead == null)
                {
                    BufferRead = new byte[BUFFER_SIZE];
                }

                if (outFileStream == null)
                {
                    CommonUtil.CreateDir(Path.GetDirectoryName(_localPath));

                    if(File.Exists(_localPath))
                    {
                        CommonUtil.Copy(_localPath, _localPath + ".old_downloaded_file_" + realOffset + "+" + (realSize - 1));
                    }

                    if (HttpDownloadRequest.isSupportContinue)
                    {
                        outFileStream = File.Open(_localPath, FileMode.Append);
                    }
                    else
                    {
                        outFileStream = File.Open(_localPath, FileMode.Create);
                    }
                }

                bool finish = false;
                while (!_isCancel)
                {
                    setStep(DownloadStep.Downloading);

                    int readyReadLen = BufferRead.Length;
                    if(_size > 0)
                    {       
                        Math.Min(BUFFER_SIZE, (int)_size - readedSum);
                    }

                    int readLen = response.Read(BufferRead, 0, readyReadLen);
                    if (readLen <= 0)
                    {
                        if (_size > 0)
                        {
                            if(readedSum < _size)
                            {
                                error = DownloadError.Unknown;
                            }
                            //								badRetryCount++;

                            break;
                        }
                        else
                        {
                            finish = true;
                        }
                    }

                    lock (this)
                    {
                        if (_isCancel)
                        {
                            break;
                        }
                        outFileStream.Write(BufferRead, 0, readLen);
                        outFileStream.Flush();
                    }

                    readedSum += readLen;
                    _progress += readLen;

//                    File.AppendAllText(_localPath + ".download_log", "read    readedSum: " + readedSum + "\n");
                    //						curRetryCount = 0;
                    if (_size > 0)
                    {
                        if (readedSum == _size)
                        {
                            setStep(DownloadStep.Downloaded);
                            downloaded = true;

//                            File.AppendAllText(_localPath + ".download_log", "_size > 0 finish    readedSum: " + readedSum + "\n");
                            break;
                        }
                        else if (readedSum > _size)
                        {
                            Debug.LogError("download size error: readedSum > _size: " + _originalUrl);
                            //								sizeErrorCount++;
                            //								badRetryCount++;
                            readedSum = 0;
                            outFileStream.Close();
                            outFileStream = null;
                            CommonUtil.Delete(_localPath);

//                            File.AppendAllText(_localPath + ".download_log", "readedSum > _size    readedSum: " + readedSum + "\n");
                            break;
                        }
                    }
                    else
                    {
                        if(finish)
                        {
//                            File.AppendAllText(_localPath + ".download_log", "finish readedSum: " + readedSum + "\n");
                            setStep(DownloadStep.Downloaded);
                            downloaded = true;
                            break;
                        }
                    }
                }
            }
            catch (WebException e)
            {
                if(e.Status != WebExceptionStatus.Timeout)
                {
                    Debug.LogException(e);
                    if (response == null)
                    {
                        error = DownloadError.ConnectError;
                    }
                    else
                    {
                        error = DownloadError.DownloadError;
                    }
                }
            }
            catch (SizeException e)
            {
                Debug.LogException(e);
                error = DownloadError.DownloadSizeError;
            }
            catch (EncodingException e)
            {
                Debug.LogException(e);
                error = DownloadError.DownloadEncodingError;
            }
            catch (IOException e)
            {
                Debug.LogException(e);
                error = DownloadError.WriteOuputFileError;
            }
            catch (Exception e)
            {
                Debug.LogException(e);
                error = DownloadError.Unknown;
            }
            finally
            {
                if (response != null)
                {
                    response.Close();
                    response = null;
                }
            }
        }
        while (false);





        if (outFileStream != null)
        {
            outFileStream.Close();
            outFileStream = null;
        }

//        if (downloaded)
//        {
//            if (!string.IsNullOrEmpty(this.hash))
//            {
//                string fileHash = MD5Hash.GetMD5HashFromFile(_localPath);
//                if (fileHash != this.hash)
//                {
//                    Debug.LogError("hash of downloaded file is not same: " + _localPath + "  " + this.hash + "  " + fileHash);
//                    downloaded = false;
//                    CommonUtil.Delete(_localPath);
//                    error = DownloadError.HashFailed;
//                }
//            }
//        }

        if (downloaded)
        {
            repsonseSuccess();
        }
        else
        {
            if (error == DownloadError.None)
            {
                error = DownloadError.Unknown;
            }
            responseError(error);
        }
    }

    public void Abort()
    {
        this._isCancel = true;
        if (this._request != null)
        {
            this._request.Abort();
        }
    }
}
